package lxs.swift.tool.acc;


import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.function.*;

/**
 * 累加接口
 *
 * @param <I>输入
 * @param <M>中间状态
 * @param <O>输出
 */
public interface Acc<I, M, O> {
    M init();

    /**
     * 输入参数
     */
    M input(I in, M state);

    /**
     * 输出的结果
     */
    O output(M state);

    /**
     * 重置
     */
    default M reset(M state) {
        return init();
    }


    /**
     * 每一步都通过刷新器刷新中间状态的值,然后通过映射这个中间状态得到最终的值
     *
     * @param initial   初始中间状态
     * @param refresher 刷新器  中间状态+输入=新中间状态
     * @param map       映射器 中间状态->结果
     * @param reset     重置状态
     */
    static <I, M, O> Acc<I, M, O> refresh(@NotNull Supplier<M> initial, @NotNull BiFunction<I, M, M> refresher, @NotNull Function<M, O> map, @NotNull Function<M, M> reset) {
        Objects.requireNonNull(refresher);
        Objects.requireNonNull(map);
        Objects.requireNonNull(reset);
        return new Acc<I, M, O>() {
            @Override
            public M init() {
                return initial.get();
            }

            @Override
            public M input(I in, M state) {
                return refresher.apply(in, state);
            }

            @Override
            public O output(M state) {
                return map.apply(state);
            }

            @Override
            public M reset(M state) {
                return reset.apply(state);
            }
        };
    }

    /**
     * 当第一次输入时使用第一次的输入计算状态
     *
     * @param init      通过初次输入初始化中间状态
     * @param refresher 刷新器  中间状态+输入=新中间状态
     * @param map       映射器 中间状态->结果
     */
    static <I, M, O> Acc<I, M, O> refreshLazy(@NotNull Function<I, M> init, @NotNull BiFunction<I, M, M> refresher, @NotNull Function<M, O> map) {
        Objects.requireNonNull(init);
        Objects.requireNonNull(refresher);
        Objects.requireNonNull(map);
        return new Acc<I, M, O>() {
            boolean isFirst = true;

            @Override
            public M init() {
                return null;
            }

            @Override
            public M input(I in, M state) {
                if (isFirst) {
                    isFirst = false;
                    return init.apply(in);
                }
                return refresher.apply(in, state);
            }

            @Override
            public O output(M state) {
                return map.apply(state);
            }

            @Override
            public M reset(M state) {
                isFirst = true;
                return null;
            }
        };
    }

    /**
     * 归并聚合
     *
     * @param initial        初始中间状态
     * @param map            映射器 中间状态->结果
     * @param binaryOperator 聚合
     * @param reset          重置状态
     */
    static <I, O> Acc<I, O, O> reduce(@NotNull O initial, @NotNull Function<I, O> map, @NotNull BinaryOperator<O> binaryOperator, @NotNull Function<O, O> reset) {
        Objects.requireNonNull(binaryOperator);
        Objects.requireNonNull(map);
        Objects.requireNonNull(reset);
        return Acc.refresh(() -> initial, (in, state) -> {
            O o = map.apply(in);
            return binaryOperator.apply(o, state);
        }, Function.identity(), reset);
    }


    /**
     * 当判断结果为true时计数加一
     *
     * @param predicate 判断器
     */
    static <I> Acc<I, Long, Long> count(@NotNull Predicate<I> predicate) {
        Objects.requireNonNull(predicate);
        return Acc.refresh(() -> 0L, (in, state) -> {
            boolean test = predicate.test(in);
            if (test) return state + 1L;
            return state;
        }, Function.identity(), (m) -> 0L);
    }

    /**
     * 将映射器产生的中间值存入集合
     *
     * @param collection 非空集合
     * @param map        泛型映射器
     */
    static <I, R, C extends Collection<R>> Acc<I, C, C> collect(@NotNull C collection, @NotNull Function<I, R> map) {
        Objects.requireNonNull(collection);
        Objects.requireNonNull(map);
        return Acc.refresh(() -> collection, (in, state) -> {
            R r = map.apply(in);
            state.add(r);
            return state;
        }, Function.identity(), (state) -> {
            state.clear();
            return state;
        });
    }


    interface Int {
        static <T> Acc<T, long[], Long> avg(Function<T, Long> map) {
            return Acc.refresh(() -> new long[]{0L, 0L}, (in, values) -> {
                Long v = map.apply(in);
                if (v == null) return values;
                values[0] += v;
                values[1]++;
                return values;
            }, values -> values[0] / values[1], (values) -> {
                values[0] = 0;
                values[1] = 0;
                return values;
            });
        }

        static <T> Acc<T, Long, Long> sum(Function<T, Long> map) {
            return Acc.reduce(0L, map, Long::sum, (s) -> 0L);
        }

        static <T> Acc<T, Long, Long> product(Function<T, Long> map) {
            return Acc.reduce(1L, map, (a, b) -> a * b, (s) -> 1L);
        }

        static <T> Acc<T, Long, Long> min(Function<T, Long> map) {
            return Acc.refreshLazy(map, (in, state) -> {
                Long s = map.apply(in);
                return Math.min(s, state);
            }, Function.identity());
        }

        static <T> Acc<T, Long, Long> max(Function<T, Long> map) {
            return Acc.refreshLazy(map, (in, state) -> {
                Long s = map.apply(in);
                return Math.max(s, state);
            }, Function.identity());
        }

        //中间值
        static <T> Acc<T, ArrayList<Long>, Long> median(Function<T, Long> map) {
            return Acc.refresh(ArrayList::new, (in, doubles) -> {
                Long v = map.apply(in);
                if (v == null) return doubles;
                doubles.add(v);
                return doubles;
            }, values -> {
                Collections.sort(values);
                int midI = values.size() / 2;
                if (values.size() % 2 == 0) {
                    return (values.get(midI) + values.get(midI - 1)) / 2;
                }
                return values.get(midI);
            }, (s) -> new ArrayList<>());
        }
    }

    interface Dec {
        static <T> Acc<T, double[], Double> avg(Function<T, Double> map) {
            return Acc.refresh(() -> new double[]{0.0, 0.0}, (in, values) -> {
                Double v = map.apply(in);
                if (v == null) return values;
                values[0] += v;
                values[1]++;
                return values;
            }, values -> values[0] / values[1], (values) -> {
                values[0] = 0;
                values[1] = 0;
                return values;
            });
        }

        static <T> Acc<T, Double, Double> sum(Function<T, Double> map) {
            return Acc.reduce(0.0, map, Double::sum, (s) -> 0.0);
        }

        static <T> Acc<T, Double, Double> product(Function<T, Double> map) {
            return Acc.reduce(1.0, map, (a, b) -> a * b, (s) -> 1.0);
        }

        static <T> Acc<T, Double, Double> min(Function<T, Double> map) {
            return Acc.refreshLazy(map, (in, state) -> {
                Double s = map.apply(in);
                return Math.min(s, state);
            }, Function.identity());
        }

        static <T> Acc<T, Double, Double> max(Function<T, Double> map) {
            return Acc.refreshLazy(map, (in, state) -> {
                Double s = map.apply(in);
                return Math.max(s, state);
            }, Function.identity());
        }

        //中间值
        static <T> Acc<T, ArrayList<Double>, Double> median(Function<T, Double> map) {
            return Acc.refresh(ArrayList::new, (in, doubles) -> {
                Double v = map.apply(in);
                if (v == null) return doubles;
                doubles.add(v);
                return doubles;
            }, values -> {
                Collections.sort(values);
                int midI = values.size() / 2;
                if (values.size() % 2 == 0) {
                    return (values.get(midI) + values.get(midI - 1)) / 2;
                }
                return values.get(midI);
            }, (s) -> new ArrayList<>());

        }
    }
}
